package com.reger.web.exception;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.MimeType;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.reger.core.Context;
import com.reger.core.exception.GlobalException;
import com.reger.core.exception.GlobalExceptionStatus;
import com.reger.core.exception.Level;
import com.reger.core.model.ResponseEntity;
import com.reger.core.utils.HttpOutJson;
import com.reger.core.utils.JSONUtils;
import com.reger.core.utils.ListUtils;
import com.reger.core.utils.StringUtils;

/**
 * 全局API异常
 *
 */
@ControllerAdvice
public class GlobalApiExceptionHandler {

	private final static Logger logger = LoggerFactory.getLogger("exception.log");
	private static String errorPath;

	@Value("${spring.web.error-path:error}")
	public void setErrorPath(String errorPath) {
		GlobalApiExceptionHandler.errorPath = errorPath;
	}

	@ExceptionHandler(GlobalException.class)
	public ModelAndView handleControllerException(HttpServletRequest request, HttpServletResponse response,
			GlobalException ex) {
		ResponseEntity<?> responseEntity = ResponseEntity.fail(ex);
		return GlobalApiExceptionHandler.outException(request, response, responseEntity, ex);
	}

	@ExceptionHandler(IllegalStateException.class)
	public ModelAndView handleControllerException(HttpServletRequest request, HttpServletResponse response,
			IllegalStateException ex) {
		String message = StringUtils.isEmpty(ex.getMessage()) ? "操作失败" : ex.getMessage();
		ResponseEntity<?> responseEntity = ResponseEntity.fail(GlobalExceptionStatus.INFO, message, null);
		return GlobalApiExceptionHandler.outException(request, response, responseEntity, ex);
	}

	@ExceptionHandler(MethodArgumentNotValidException.class)
	public ModelAndView handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest request,
			HttpServletResponse response) {
		if (GlobalApiExceptionHandler.isJson(request)) {
			ResponseEntity<?> responseEntity = ResponseEntity.fail(GlobalExceptionStatus.VALID_FAIL);
			responseEntity.setDetails(geJson(ex, request));
			HttpOutJson.out(responseEntity, HttpStatus.BAD_REQUEST.value());
			return null;
		} else {
			return GlobalApiExceptionHandler.outException(request, response, ex);
		}
	}

	@ExceptionHandler(Throwable.class)
	public ModelAndView ControllerException(HttpServletRequest request, HttpServletResponse response, Throwable ex) {
		return GlobalApiExceptionHandler.outException(request, response, ex);
	}

	public static ModelAndView outException(HttpServletRequest request, HttpServletResponse response, Throwable ex) {
		String errorMsg = "操作失败";
		ResponseEntity<?> responseEntity = ResponseEntity.fail(GlobalExceptionStatus.ERROR, errorMsg, null);
		return GlobalApiExceptionHandler.outException(request, response, responseEntity, ex);
	}

	public static ModelAndView outException(HttpServletRequest request, HttpServletResponse response,
			ResponseEntity<?> responseEntity, Throwable ex) {

		HttpStatus status = getHttpStatus(ex);
		if ((status != HttpStatus.INTERNAL_SERVER_ERROR && status != HttpStatus.OK)) {
			responseEntity.setLevel(Level.INFO);
		}
		GlobalApiExceptionHandler.logLevelOut(responseEntity, ex);
		if (GlobalApiExceptionHandler.isJson(request)) {
			HttpOutJson.out(responseEntity, status.value());
			return null;
		}
		return controllerFromRequestException(request, response, status, responseEntity, ex);
	}

	private static void logLevelOut(ResponseEntity<?> responseEntity, Throwable ex) {
		ListUtils<Object> lu = ListUtils.n().a(responseEntity.getLevel()).a(responseEntity.getMessage())
				.a(Context.getContext());
		String format = "" + "\n    >>{}<< 系统出现了异常 ********* (( {} ))*********" + "\n    >>>>>> {}";
		switch (responseEntity.getLevel()) {
		case OFF:
			return;
		case ERROR:
			logger.error(format, lu.a(ex).to().toArray());
			break;
		case WARN:
			logger.warn(format, lu.to().toArray());
			break;
		case INFO:
			logger.info(format, lu.to().toArray());
			break;
		case DEBUG:
			logger.info(format, lu.to().toArray());
			break;
		case TRACE:
			logger.trace(format, lu.to().toArray());
			break;
		default:
			break;
		}
	}

	/**
	 * 判断是否需要响应json
	 * 
	 * @param request
	 * @return
	 */
	private static boolean isJson(HttpServletRequest request) {
		if (Context.isRest())
			return true;
		String contenttype = request.getContentType();
		if (StringUtils.isNotEmpty(contenttype)
				&& "json".equals(MimeType.valueOf(contenttype).getSubtype().toLowerCase())) {
			return true;
		}
		;
		String requestedWith = request.getHeader("X-Requested-With");
		return requestedWith != null ? "XMLHttpRequest".equals(requestedWith) : false;

	}

	@SuppressWarnings("unchecked")
	private static Map<String, Object> getModelMap(ResponseEntity<?> responseEntity, Throwable ex) {
		Map<String, Object> map = BeanMap.create(responseEntity);
		map.put("timestamp", responseEntity.getCurtime());
		map.put("status", responseEntity.getCode());
		map.put("entity", responseEntity);
		map.put("error", ex);
		return map;
	}

	/**
	 * 响应异常页面
	 * 
	 * @param request
	 * @param response
	 * @param e
	 * @return
	 */
	private static ModelAndView controllerFromRequestException(HttpServletRequest request, HttpServletResponse response,
			HttpStatus status, ResponseEntity<?> responseEntity, Throwable ex) {
		response.setStatus(status.value());
		return new ModelAndView(errorPath, getModelMap(responseEntity, ex));
	}

	private String geJson(MethodArgumentNotValidException ex, HttpServletRequest request) {
		List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
		Map<String, String> errors = new HashMap<>(fieldErrors.size());
		fieldErrors.forEach(error -> {
			errors.put(error.getField(), error.getDefaultMessage());
		});
		return JSONUtils.toJSONString(errors);
	}

	@SuppressWarnings("deprecation")
	public static final HttpStatus getHttpStatus(Throwable ex) {
		HttpStatus status = null;
		if (ex == null || ex instanceof GlobalException || ex instanceof IllegalStateException) {
			status = HttpStatus.OK;
		} else if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
			status = HttpStatus.NOT_FOUND;
		} else if (ex instanceof HttpRequestMethodNotSupportedException) {
			status = HttpStatus.METHOD_NOT_ALLOWED;
		} else if (ex instanceof HttpMediaTypeNotSupportedException) {
			status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
		} else if (ex instanceof HttpMediaTypeNotAcceptableException) {
			status = HttpStatus.NOT_ACCEPTABLE;
		} else if (ex instanceof MissingPathVariableException) {
			status = HttpStatus.INTERNAL_SERVER_ERROR;
		} else if (ex instanceof MissingServletRequestParameterException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof ServletRequestBindingException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof ConversionNotSupportedException) {
			status = HttpStatus.INTERNAL_SERVER_ERROR;
		} else if (ex instanceof TypeMismatchException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof HttpMessageNotReadableException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof HttpMessageNotWritableException) {
			status = HttpStatus.INTERNAL_SERVER_ERROR;
		} else if (ex instanceof MethodArgumentNotValidException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof MissingServletRequestPartException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof BindException) {
			status = HttpStatus.BAD_REQUEST;
		} else if (ex instanceof NoHandlerFoundException) {
			status = HttpStatus.NOT_FOUND;
		} else if (ex instanceof AsyncRequestTimeoutException) {
			status = HttpStatus.SERVICE_UNAVAILABLE;
		} else {
			status = HttpStatus.INTERNAL_SERVER_ERROR;
		}
		return status;
	}
}
